import re
import sys

from PyQt4 import QtCore, QtGui

from BeautifulSoup import BeautifulSoup
from PyStackExchange import stackexchange 

from ui_ImportSEDialog import Ui_ImportSEDialog

from app import Application
import py_helpers

# Beginnings of StackOverflow API add-ons

def api_domain_for(domain):
    """
    Gets the API endpoint domain for the given end-user domain.
    """
    # TODO: Is this always going to work, or should I be querying StackAuth...?
    return "api.%s" % domain

def parse_se_url(url):
    """
    Attempts to parse url as a reference to a StackExchange question or answer.
    Returns the tuple (domain, question_id, answer_id), where answer_id is None
    if it's a question, upon a successful parse.  Upon an unsuccessful parse,
    returns (None, None, None).
    """
    question_url_re = re.compile(r"^http://([^/]+)/questions/(\d+)(?:/[^/]*)?$")
    answer_url_re = re.compile(r"^http://([^/]+)/questions/(\d+)(?:/[^/]+)?/(\d+)(?:#\d+)?$")
    
    m = answer_url_re.search(url)
    if m:
        return api_domain_for(m.group(1)), int(m.group(2)), int(m.group(3))
    
    m = question_url_re.search(url)
    if m:
        return api_domain_for(m.group(1)), int(m.group(2)), None
    
    return None, None, None

class ImportSEDialog(QtGui.QDialog, Ui_ImportSEDialog):
    """
    Dialog for importing code from a StackExchange site.
    
    If the import was successful, then on return, the following attributes
    are set:
    - code
    - language - one of the languages from EnvironmentList, in string form
    """
    def __init__(self, parent, environment_list, default_language = None):
        QtGui.QDialog.__init__(self, parent)
        self.setupUi(self)
        self.CodePreview.setReadOnly(True)
        self.DelayedImportTimer = QtCore.QTimer()
        self.DelayedImportTimer.setSingleShot(True)
        
        self.environment_list = environment_list

        self.buttonBox.button(QtGui.QDialogButtonBox.Ok).setEnabled(False)

        self.UrlEdit.editingFinished.connect(self.import_)
        self.UrlEdit.textChanged.connect(self.delayed_import)
        self.DelayedImportTimer.timeout.connect(self.import_)
        self.SectionCombo.currentIndexChanged[int].connect(self.section_changed)
        self.LanguageCombo.currentIndexChanged[str].connect(self.language_changed)

        # TODO: Ought to make environment_list an item model for QComboBox...
        for language in sorted(environment_list.keys()):
            self.LanguageCombo.addItem(language)
        if default_language:
            index = self.LanguageCombo.findText(default_language)
            if index != -1:
                self.LanguageCombo.setCurrentIndex(index)
        
        self.tried_import = False
        self.clear()

    def showEvent(self, event):
        clipboard = QtGui.QApplication.clipboard()
        text = ""
        if clipboard.supportsSelection():
            text = clipboard.text(clipboard.Selection)
        if text == "":
            text = clipboard.text(clipboard.Clipboard)
        if text != "" and re.match(r"\s*http:", text):
            self.UrlEdit.setText(text)
            self.import_()

    def show_error(self, message):
        self.StatusLabel.setText("<img src='%s'/> %s" %
                                 (Application.image_path("silk", "exclamation.png"), message))
        self.buttonBox.button(QtGui.QDialogButtonBox.Ok).setEnabled(False)

    def show_warning(self, message):
        self.StatusLabel.setText("<img src='%s'/> %s" %
                                 (Application.image_path("silk", "error.png"), message))

    def show_success(self, message):
        self.StatusLabel.setText("<img src='%s'/> %s" %
                                 (Application.image_path("silk", "accept.png"), message))
        self.buttonBox.button(QtGui.QDialogButtonBox.Ok).setEnabled(True)

    def clear(self):
        self.QuestionLabel.setText('None')
        self.TagsLabel.setText('None')
        self.SectionCombo.clear()
        self.SectionCombo.addItem("None")
        self.SectionCombo.setEnabled(False)
        self.LanguageCombo.setEnabled(False)
        self.CodePreview.setText("")

    def delayed_import(self, text):
        self.tried_import = False
        self.DelayedImportTimer.start(750)

    def import_(self):
        """
        Tries to import the requested URL.
        """
        if self.tried_import:
            return
        self.tried_import = True
        self.clear()
        if self.DelayedImportTimer.isActive():
            self.DelayedImportTimer.stop()
        
        url = str(self.UrlEdit.text()).strip()
        if url == "":
            self.StatusLabel.setText("Please enter a URL.")
            self.buttonBox.button(QtGui.QDialogButtonBox.Ok).setEnabled(False)
            return
        
        domain, question_id, answer_id = parse_se_url(url)
        if domain is None:
            self.show_error("Not a StackExchange question or answer URL.")
            return
        
        site = stackexchange.Site(domain, Application.stackexchange_api_key)
        
        try:
            QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor))
            if answer_id:
                question = site.question(question_id)
                answer = site.answer(answer_id, body=True)
            else:
                question = site.question(question_id, body=True)
                answer = None
        except:
            QtGui.QApplication.restoreOverrideCursor()
            self.show_error("Failed to retrieve %s." % 
                            "answer" if answer_id else "question")
            return
        finally:
            QtGui.QApplication.restoreOverrideCursor()

        if answer:
            body = answer.body
        else:
            body = question.body

        soup = BeautifulSoup(body, convertEntities=BeautifulSoup.ALL_ENTITIES)
        code_list = [ tag('code')[0] for tag in soup('pre') ]

        self.QuestionLabel.setText(question.title)
        self.TagsLabel.setText(", ".join(question.tags))

        if not code_list:
            self.show_error("Failed to find any code.")
            return

        self.LanguageCombo.setEnabled(True)
        self.SectionCombo.setEnabled(True)
        
        code_list = [ tag.string for tag in code_list if tag.string ]
        self.SectionCombo.clear()
        for section in code_list:
            section_lines = section.splitlines()
            self.SectionCombo.addItem("%s... (%i line%s)" % 
                                      (section_lines[0], len(section_lines), "s" if len(section_lines) > 1 else ""), 
                                      str(section))
        self.SectionLabel.setVisible(len(code_list) > 1)
        self.SectionCombo.setVisible(len(code_list) > 1)

        for tag, language in py_helpers.product(question.tags, self.environment_list.keys()):
            if tag.lower() == language.lower():
                self.LanguageCombo.setCurrentIndex(self.LanguageCombo.findText(language))
                break
        else:
            self.show_warning("No matching language found")
        
        self.show_success("Ready.")

    def section_changed(self, index):
        self.code = str(self.SectionCombo.itemData(index).toString())
        self.CodePreview.setText(self.code)

    def language_changed(self, text):
        text = str(text)
        # Grab the lexer from the first environment we can find;
        # they should all be the same.
        # TODO: Now that languages exist outside of environments, it'd be cleaner to just look up a language
        lexer = self.environment_list[text].values()[0][0].language.lexer
        self.CodePreview.setLexer(lexer(self.CodePreview))
        self.language = text

if __name__ == '__main__':
    qt_app = QtGui.QApplication(sys.argv)
    import_se = ImportSEDialog(None)
    import_se.show()
    result = qt_app.exec_()
    sys.exit(result)
